#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include "imbus_internal.h"
#include "im_server_unix.h"
#include "im_address_unix.h"
#include "im_io_unix.h"

struct _IMServerUnix
{
    IMServer        parent_object;

    IMMainLoop      *main_loop;
    IMInt            listen_fd;
    IMInt            watch_id;
    IMAddressUnix   *address;
    IMNewConnectionHandler handler;
    IMPointer        data;
    IMDestroyFunc    data_destroy_func;
};

struct _IMServerUnixClass
{
    IMServerClass   parent_class;
};

static IMBool
__im_server_unix_watch_callback (IMMainLoop *loop, IMInt watch_id,
                                 IMPointer data)
{
    IMServerUnix *p = IM_SERVER_UNIX(data);
    IMInt fd;
    struct sockaddr *addr;
    socklen_t len;

    addr = (struct sockaddr*)im_address_unix_get_data (p->address);
    len = im_address_unix_get_data_length (p->address);
    fd = accept (p->listen_fd, addr, &len);
    if (fd >= 0) {
        IMIOChannel *channel = im_io_channel_unix_new (fd);
        im_io_channel_set_close_on_unref (channel, TRUE);
        if (p->handler)
            p->handler ((IMServer*)p, channel, p->data);
        im_object_unref (channel);
    }
    return TRUE;
}

static IMBool
__im_server_unix_is_connected (IMServer *server)
{
    IMServerUnix *p = IM_SERVER_UNIX(server);
    _im_assert(p);
    return p->listen_fd >= 0;
}

static void
__im_server_unix_disconnect (IMServer *server)
{
    IMServerUnix *p = IM_SERVER_UNIX(server);
    _im_assert(p);

    if (p->listen_fd >= 0)
        close (p->listen_fd);
    if (p->watch_id >= 0 && p->main_loop)
        im_main_loop_remove_watch (p->main_loop, p->watch_id);
    if (p->address) {
        im_address_unix_remove_related_file (p->address);
        im_object_unref (p->address);
    }
    p->watch_id = -1;
    p->listen_fd = -1;
    p->address = 0;
}

static IMBool
__im_server_unix_listen (IMServer     *server,
                         const IMChar *address,
                         IMIOError    *error)
{
    IMServerUnix  *p = IM_SERVER_UNIX (server);
    IMAddressUnix *addr = IM_ADDRESS_UNIX(im_address_unix_new (address));
    _im_assert(p && addr);

    im_object_ref (addr);
    if (im_address_valid (IM_ADDRESS(addr))) {
        IMInt fd = -1;
        switch (im_address_unix_get_type (addr)) {
            case IM_ADDRESS_UNIX_LOCAL:
                fd = socket (PF_UNIX, SOCK_STREAM, 0);
                break;
            case IM_ADDRESS_UNIX_INET:
                fd = socket (PF_INET, SOCK_STREAM, 0);
                break;
            default:
                im_object_unref (addr);
                return FALSE;
        }

        if (fd < 0) {
            if (error) *error = im_io_channel_error_from_errno (errno);
            im_object_unref (addr);
            return FALSE;
        }

        im_address_unix_remove_related_file (addr);

        if (bind (fd,
                  (const struct sockaddr*)im_address_unix_get_data (addr),
                  im_address_unix_get_data_length (addr)) != 0) {
            close (fd);
            if (error) *error = im_io_channel_error_from_errno (errno);
            im_object_unref (addr);
            return FALSE;
        }

        im_address_unix_secure_related_file (addr);

        if (listen (fd, 64) < 0) {
            close (fd);
            if (error) *error = im_io_channel_error_from_errno (errno);
            im_address_unix_remove_related_file (addr);
            im_object_unref (addr);
            return FALSE;
        }

        __im_server_unix_disconnect (server);
        p->listen_fd = fd;
        p->address = addr;

        if (p->main_loop) {
            IMIOChannel *channel = im_io_channel_unix_new (fd);
            im_io_channel_set_close_on_unref (channel, FALSE);
            p->watch_id =
                im_main_loop_add_io_read_watch (p->main_loop, channel,
                                                __im_server_unix_watch_callback,
                                                (IMPointer)p,
                                                0);
            im_object_unref (channel);
        }
        return TRUE;
    }
    im_object_unref (addr);
    return FALSE;
}

static void
__im_server_unix_set_main_loop (IMServer *server, IMMainLoop *loop)
{
    IMServerUnix *p = IM_SERVER_UNIX (server);
    _im_assert(p && loop);

    if (p->main_loop) {
        if (p->watch_id >= 0)
            im_main_loop_remove_watch (p->main_loop, p->watch_id);
        im_object_unref (p->main_loop);
    }

    p->main_loop = im_object_ref (loop);
    if (p->listen_fd >= 0) {
        IMIOChannel *channel = im_io_channel_unix_new (p->listen_fd);
        im_io_channel_set_close_on_unref (channel, FALSE);
        p->watch_id =
            im_main_loop_add_io_read_watch (p->main_loop, channel,
                                            __im_server_unix_watch_callback,
                                            (IMPointer)p,
                                            0);
        im_object_unref (channel);
    } else {
        p->watch_id = -1;
    }
}

static IMMainLoop *
__im_server_unix_get_main_loop (IMServer *server)
{
    IMServerUnix *p = IM_SERVER_UNIX (server);
    _im_assert(p);

    return p->main_loop;
}

static void
__im_server_unix_set_new_connection_handler (IMServer *server,
                                             IMNewConnectionHandler handler,
                                             IMPointer data,
                                             IMDestroyFunc data_destroy_func)
{
    IMServerUnix *p = IM_SERVER_UNIX (server);
    _im_assert(p);

    if (p->data_destroy_func)
        p->data_destroy_func (p->data);
    p->handler = handler;
    p->data = data;
    p->data_destroy_func = data_destroy_func;
}

static void
__im_server_unix_class_init (IMPointer klass)
{
    IMServerClass *scp = (IMServerClass *)klass;

    scp->listen             = __im_server_unix_listen;
    scp->is_connected       = __im_server_unix_is_connected;
    scp->disconnect         = __im_server_unix_disconnect;
    scp->set_main_loop      = __im_server_unix_set_main_loop;
    scp->get_main_loop      = __im_server_unix_get_main_loop;
    scp->set_new_connection_handler = __im_server_unix_set_new_connection_handler;
}

static void
__im_server_unix_initialize (IMPointer obj)
{
    IMServerUnix *p = (IMServerUnix *) obj;
    p->watch_id = -1;
    p->listen_fd = -1;
    p->main_loop = 0;
    p->address = 0;
    p->handler = 0;
    p->data = 0;
    p->data_destroy_func = 0;
}

static void
__im_server_unix_finalize (IMPointer obj)
{
    IMServerUnix *p = (IMServerUnix *) obj;
    __im_server_unix_disconnect (IM_SERVER(p));

    if (p->main_loop)
        im_object_unref (p->main_loop);
    if (p->data_destroy_func)
        p->data_destroy_func (p->data);
}

void
__im_type_register_server_unix ()
{
    static IMTypeInfo im_server_unix_type_info =
    {
        sizeof (IMServerUnixClass),  /**< class_size >**/
        __im_server_unix_class_init, /**< class_init >**/
        0,                           /**< class_finalize >**/

        sizeof (IMServerUnix),       /**< instance_size >**/
        __im_server_unix_initialize, /**< instance_init >**/
        __im_server_unix_finalize,   /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_SERVER,
                                      IM_TYPE_SERVER_UNIX,
                                      "ServerUnix",
                                      &im_server_unix_type_info,
                                      TRUE);
}

IMServerUnix*
im_server_unix_new ()
{
    IMServerUnix *server_unix;
    server_unix = (IMServerUnix *) im_object_new (IM_TYPE_SERVER_UNIX);
    _im_assert (server_unix);
    return server_unix;
}

IMServerUnix*
im_server_unix_new_listen (const IMChar *address, IMIOError *error)
{
    IMServerUnix *server_unix;
    _im_return_val_if_fail (address != 0, 0);
    server_unix = (IMServerUnix *) im_object_new (IM_TYPE_SERVER_UNIX);
    _im_assert (server_unix);
    if (!__im_server_unix_listen ((IMServer*)server_unix, address, error)) {
        im_object_unref (server_unix);
        server_unix = 0;
    }
    return server_unix;
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
